﻿using System;

using System.Collections.Generic;
using System.Text;
using Silvermoon.Controls;
using System.Drawing;
using Silvermoon.Windows;
using System.Diagnostics;
using Silvermoon.Controls.Base;

namespace Silvermoon.Core
{
    public partial class MessageServer
    {

        #region fields

        private int lastTap;
        private Control lastTopTapControl;

        private List<Control> touchedControls = new List<Control>(20);
        private Dictionary<Control, Point> screenOffsets = new Dictionary<Control, Point>();
        private TouchEventArgs touchEventArgs;
        private PanEventArgs panEventArgs;
        private UITimer holdTimer;

        #endregion
        #region ctor

        #endregion

        private Control GetTopControl()
        {
            return touchedControls.Count > 0 ? touchedControls[0] : null;
        }

        private Control GetBottomControl()
        {
            int n = touchedControls.Count - 1;
            return n >= 0 ? touchedControls[n] : null;
        }

        void OnLongPressTick(object sender, EventArgs e)
        {
            touchEventArgs.IsLongPressed = true;
            holdTimer.Stop();
            if (touchEventArgs != null)
            {
                touchEventArgs.UpdateDuration();
                touchEventArgs.ClearFlags();
                Control control = GetTopControl();
                while (control != null)
                {
                    if (control.IsHoldEnabled && HitTestControl(control, touchEventArgs.Position)) control.OnHold(touchEventArgs);
                    control = control.Parent;
                }

                if (!touchEventArgs.Handled)
                {
                    touchEventArgs.IsTouched = false;
                    foreach (Control c in touchedControls) c.OnTouchUp(touchEventArgs);
                    touchEventArgs.ClearFlags();
                    touchEventArgs.IsTouched = false;

                    touchedControls.Clear();
                    screenOffsets.Clear();
                } touchEventArgs.ClearFlags();
            }
        }

        private void OnMouseDown(object sender, System.Windows.Forms.MouseEventArgs e)
        {
            try
            {
                holdTimer.Restart();
                Point p = Control.ScaledPoint(e.X, e.Y);
                GetHitTestControls(p.X, p.Y);
                touchEventArgs = new TouchEventArgs { IsTouched = true };
                touchEventArgs.StartPosition = p;

                if (AnyControlEnablesLongPress()) holdTimer.Start();

                touchEventArgs.ClearFlags();

                touchEventArgs.FocusSet = false;

                Control first = touchedControls.Count > 0 ? touchedControls[0] : null;
                //if (first != null) first.Window.CollapseToolbar(first);

                int tick = Environment.TickCount;
                if (lastTopTapControl == first)
                {
                    if ((tick - lastTap) < 600)
                    {
                        touchEventArgs.DoubleTap = true;
                        first = null;
                    }
                }
                lastTap = tick;
                lastTopTapControl = first;

                foreach (Control control in touchedControls)
                {
                    control.OnTouchDown(touchEventArgs);
                    if (touchEventArgs.Cancel) break;
                }
            }
            catch (Exception ex)
            {
                if (Debugger.IsAttached) throw;
                Screen.Current.RaiseUnhandledException(ex);
            }
        }

        private void FocusTopControl()
        {
            if (touchedControls.Count > 0)
            {
                Control c = touchedControls[0];
                if (c.CanFocus) Focus(c);
            }
        }

        private void OnMouseMove(object sender, System.Windows.Forms.MouseEventArgs e)
        {
            try
            {
                Point p = Control.ScaledPoint(e.X, e.Y);
                if (touchEventArgs.Position != p)
                {
                    touchEventArgs.Position = p;

                    // perform the following only when motion is not yet detected (HasMotion=false) but is detected at this place (IsMotionDetected):
                    if (!touchEventArgs.HasMotionDetected && touchEventArgs.IsMotionDetected)
                    {
                        lastTopTapControl = null;
                        holdTimer.Stop();
                        OnMotionDetected();
                    }
                    if (touchEventArgs.PanMode && !panEventArgs.Cancel)
                    {
                        panEventArgs.Handled = false;
                        panEventArgs.SetValue(touchEventArgs.Dx, touchEventArgs.Dy, touchEventArgs.Position);
                        touchEventArgs.ControlToPan.OnPan(panEventArgs);
                    }
                }
            }
            catch (Exception ex)
            {
                if (Debugger.IsAttached) throw;
                Screen.Current.RaiseUnhandledException(ex);
            }
        }

        private void OnMotionDetected()
        {
            // get the first control that supports scrolling:
            Control controlToPan = null;
            touchEventArgs.ClearFlags();
            panEventArgs = new PanEventArgs() { Direction = touchEventArgs.Direction, Value = Point.Empty };
            foreach (Control control in touchedControls)
            {
                if (control.CanPan)
                {
                    control.OnBeginPan(panEventArgs);
                    if (panEventArgs.Handled)
                    {
                        panEventArgs.StartPoint = panEventArgs.Value;
                        controlToPan = control;
                        break;
                    }
                    if (panEventArgs.Cancel) break;
                }
            }
            touchEventArgs.ControlToPan = controlToPan;

            // if there ise a control that supports scrolling, send OnTouchUp to all controls that received OnTouch:
            if (controlToPan != null)
            {
                holdTimer.Stop();
                touchEventArgs.ClearFlags();
                foreach (Control control in touchedControls)
                {
                    if (controlToPan != control) control.OnTouchUp(touchEventArgs);
                }
                touchEventArgs.PanMode = true;
                touchEventArgs.IsTouched = false;
                panEventArgs.ClearFlags();
            }
        }

        private void OnMouseUp(object sender, System.Windows.Forms.MouseEventArgs e)
        {
            try
            {
                holdTimer.Stop();
                if (touchedControls.Count > 0)
                {
                    double v = touchEventArgs.Velocity;
                    touchEventArgs.UpdateDuration();
                    //  touchArgs.Position = Control.ScaledPoint(e.X, e.Y);
                    touchEventArgs.ClearFlags();
                    bool isTouched = touchEventArgs.IsTouched;
                    touchEventArgs.IsTouched = touchEventArgs.ControlToPan != null;
                    foreach (Control control in touchedControls)
                    {
                        control.OnTouchUp(touchEventArgs);
                        if (touchEventArgs.Cancel) break;
                    }
                    touchEventArgs.IsTouched = false;
                    if (isTouched && !touchEventArgs.IsTooTongPressed && !touchEventArgs.IsLongPressed)
                    {
                        touchEventArgs.ClearFlags();
                        TapEventArgs args = new TapEventArgs();

                        args.Position = touchEventArgs.Position;
                        int index = 0;
                        foreach (Control control in touchedControls)
                        {
                            if (!control.IsVisibleAndEnabled) continue;
                            if (control is FormBase && index > 0) break;
                            index++;
                            if (control.IgnoreTapHitTest() || HitTestControl(control, touchEventArgs.Position))
                            {
                                if (touchEventArgs.DoubleTap)
                                {
                                    control.OnDoubleTap(args);
                                }
                                else
                                {
                                    control.OnTap(args);
                                }
                            }
                            if (args.Cancel) break;
                        }
                    }
                    screenOffsets.Clear();
                    Control panControl = touchEventArgs.ControlToPan;
                    if (panControl != null)
                    {
                        //if (touchEventArgs.LastEventDuration < DragDurationThreshold && touchEventArgs.Distance > DragDistanceThreshold)
                        if (touchEventArgs.LastEventDuration < MaxDragDurationThreshold && touchEventArgs.Velocity >= VelocityThreshold)
                        {
                            touchEventArgs.ClearFlags();
                            panControl.OnFlick(touchEventArgs);
                        }
                        foreach (Control control in touchedControls)
                        {
                            control.OnEndPan(EventArgs.Empty);
                        }
                    }
                    touchedControls.Clear();
                }
            }
            catch (Exception ex)
            {
                if (Debugger.IsAttached) throw;
                Screen.Current.RaiseUnhandledException(ex);
            }
        }


        private bool HitTestControl(Control control, Point point)
        {
            Point offset;
            if (screenOffsets.TryGetValue(control, out offset))
            {
                point.Offset(-offset.X, -offset.Y);
                return control.ClientBounds.Contains(point);
            }
            return false;
        }

        private bool AnyControlEnablesLongPress()
        {
            foreach (Control c in touchedControls) if (c.IsHoldEnabled) return true;
            return false;
        }


        private static Rectangle maxRect = new Rectangle(0, 0, int.MaxValue, int.MaxValue);

        /// <summary>
        /// Fills mouseControls with all controls that are positive for hit test of the specified position.
        /// </summary>
        /// <param name="x">X coords for hit test.</param>
        /// <param name="y">Y coords for hit test.</param>
        private void GetHitTestControls(int x, int y)
        {
            Point p = new Point(x, y);
            touchedControls.Clear();
            screenOffsets.Clear();
            if (Controls == null) return;
            Control c = Controls.First;
            while (c != null)
            {
                AddIfHitTest(c, p, maxRect);
                c = c.NextControl;
            }
            Control hitControl = touchedControls.Count > 0 ? touchedControls[touchedControls.Count - 1] : null;
            touchedControls.Clear();
            while (hitControl != null)
            {
                touchedControls.Add(hitControl);
                hitControl = hitControl.Parent;
            }
        }

        /// <summary>
        /// Adds the specified control to the mouseControls list if a hit test is positive and checkes all descendant controls.
        /// </summary>
        /// <param name="c">Control to check.</param>
        /// <param name="p">Point for hit test.</param>
        private void AddIfHitTest(Control c, Point p, Rectangle inside)
        {
            if (c.Visible)
            {
                inside.Offset(c.X, c.Y);
                inside.Size = c.Size;
                Transformation transformation = c.Transformation;
                if (transformation != null)
                {
                    transformation.AdjustBounds(ref inside);
                }
                if (inside.Contains(p))
                {
                    if (c.Parent == null) touchedControls.Clear();
                    if (c.IsVisibleAndEnabled)
                    {
                        if (c.IsHitTestVisible)
                        {
                            touchedControls.Add(c);
                        }
                        screenOffsets.Remove(c);
                        screenOffsets.Add(c, inside.Location);
                        IEnumerable<Control> controls = c.GetAttachedChildren();
                        if (controls != null) foreach (Control control in controls)
                            {
                                AddIfHitTest(control, p, inside);
                            }
                    }
                }
            }
        }
    }
}

